package protocol

type IICRegInfo struct {
	RegAddr    uint8
	RegByteLen uint8
	RegValues  []byte
}

type IICConfig struct {
	DevAddr uint8 `label:"IIC Device Address" required:"true" type:"number" min:"1" max:"127"`
}

type IICState int

const (
	IICStateIdle IICState = iota
	IICStateStart
	IICStateDevAddr
	IICStateOperate
	IICStateRegAddr
	IICStateSendData
	IICStateSendAck
	IICStateReadData
	IICStateWaitAck
	IICStateStop
)

type IICSlaver struct {
	lastSCL       bool
	lastSDA       bool
	state         IICState
	stateAfterAck IICState
	isRecvAck     bool
	bitCount      uint8
	config        *IICConfig
	curByte       uint8
	curRegAddr    uint8
	curRegOffset  uint8
	Regs          map[uint8]*IICRegInfo
	OutSDA        bool
	IsOutSet      bool
	curOperate    uint8 // 0:read 1:write
	IsComplete    bool
}

func NewIICSlaver(config *IICConfig) *IICSlaver {
	return &IICSlaver{
		lastSCL:      false,
		lastSDA:      true,
		state:        IICStateIdle,
		bitCount:     0,
		config:       config,
		curRegAddr:   0,
		curRegOffset: 0,
		OutSDA:       false,
		IsOutSet:     false,
		Regs:         make(map[uint8]*IICRegInfo),
	}
}

func (iic *IICSlaver) readCurByte(byteLen uint8, SDA bool, SCL bool) bool {
	if SDA {
		iic.curByte |= (1 << (byteLen - iic.bitCount))
	}
	if !iic.lastSCL && SCL {
		// rising edge
		iic.bitCount++
		if iic.bitCount == (byteLen + 1) {
			iic.bitCount = 0
			return true
		}
	}
	return false
}

func (iic *IICSlaver) sendCurByte(SCL bool) bool {
	regInfo, ok := iic.Regs[iic.curRegAddr]
	if iic.lastSCL && !SCL {
		// falling edge
		iic.IsOutSet = false
		iic.bitCount++
		if iic.bitCount == 8 {
			iic.bitCount = 0
			return true
		}
	}
	if !iic.IsOutSet && ok && !SCL && iic.curRegOffset < regInfo.RegByteLen {
		iic.OutSDA = (regInfo.RegValues[iic.curRegOffset] & (1 << (8 - iic.bitCount))) != 0
		iic.IsOutSet = true
	}
	return false
}

/**
 * @brief Run the IIC protocol
 * @param SDA The SDA pin state
 * @param SCL The SCL pin state
 * @return SDA pin out state
 */
func (iic *IICSlaver) Run(SCL bool, SDA bool) {
	if iic.lastSCL && SCL && !iic.lastSDA && SDA {
		// stop signal
		iic.state = IICStateIdle
		iic.IsComplete = true
	}
	if iic.lastSCL && SCL && iic.lastSDA && !SDA {
		// start signal
		iic.state = IICStateStart
		iic.bitCount = 0
		iic.IsComplete = false
		iic.curRegOffset = 0
		iic.curByte = 0
		iic.IsOutSet = false
	}
	switch iic.state {
	case IICStateIdle:
		break
	case IICStateStart:
		if !iic.lastSCL && SCL {
			// rising edge
			iic.state = IICStateDevAddr
			iic.curByte = 0
		}
	case IICStateDevAddr:
		if iic.readCurByte(7, SDA, SCL) {
			if iic.curByte == iic.config.DevAddr {
				iic.state = IICStateOperate
				iic.bitCount = 0
			} else {
				iic.state = IICStateIdle
				// not this device
			}
		}
	case IICStateOperate:
		if !iic.lastSCL && SCL {
			if SDA {
				// read data
				iic.curOperate = 0
			} else {
				// write data
				iic.stateAfterAck = IICStateRegAddr
				iic.state = IICStateSendAck
				iic.curOperate = 1
			}
		}
	case IICStateSendAck:
		if !SCL && !iic.IsOutSet {
			iic.OutSDA = false
			iic.IsOutSet = true
		} else if iic.lastSCL && !SCL && iic.IsOutSet {
			// rising edge
			iic.bitCount = 0
			iic.curByte = 0
			iic.IsOutSet = false
			if iic.curOperate == 0 {
				iic.state = iic.stateAfterAck
				iic.stateAfterAck = IICStateIdle
			}
		} else if !iic.lastSCL && SCL && iic.IsOutSet {
			iic.state = iic.stateAfterAck
			iic.stateAfterAck = IICStateIdle
		}
	case IICStateWaitAck:
		if iic.lastSCL && !SCL && iic.isRecvAck {
			// falling edge
			iic.state = iic.stateAfterAck
			iic.stateAfterAck = IICStateIdle
			iic.isRecvAck = false
		}
		if SCL && !SDA {
			iic.isRecvAck = true
		}
	case IICStateRegAddr:
		if iic.readCurByte(8, SDA, SCL) {
			iic.curRegAddr = iic.curByte
			iic.curRegOffset = 0
			if iic.curOperate == 0 {
				iic.state = IICStateSendAck
				// master read, this slave write
				iic.stateAfterAck = IICStateSendData
			} else {
				iic.state = IICStateSendAck
				// master write, this slave read
				iic.stateAfterAck = IICStateReadData
			}
		}
	case IICStateReadData:
		if iic.readCurByte(8, SDA, SCL) {
			regInfo, ok := iic.Regs[iic.curRegAddr]
			if ok && iic.curRegOffset < regInfo.RegByteLen {
				regInfo.RegValues[iic.curRegOffset] = iic.curByte
			}
			iic.state = IICStateSendAck
			iic.stateAfterAck = IICStateReadData
			iic.curRegOffset++
		}
	case IICStateSendData:
		if iic.sendCurByte(SCL) {
			// rising edge
			iic.state = IICStateWaitAck
			iic.stateAfterAck = IICStateSendData
			iic.isRecvAck = false
			iic.curRegOffset++
		}
	}
	iic.lastSCL = SCL
	iic.lastSDA = SDA
}
